/** 
 * Block.java
 * PROJECT 3: Sliding Block Puzzle Solver
 * @author Jonathan Kotker	cs61b-ar
 * @author Wei Zhong Yeh	cs61b-av
 * 
 * A block defined by two intervals.
 */

public class Block {

	private Interval myRowInterval, myColInterval;
	// for debugging purposes
	private static boolean blockMovementFlag = false;
	
	public Block ( ) {
		this (1, 1, 0, 0);
	}
	
	/**
	 * Constructor to construct a block with the given length, width, and coordinates
	 * of its upper left corner of the block it references
	 * @param length	length of this block
	 * @param width		width of this block
	 * @param rowPos	row position of the upper left corner of 'block'
	 * @param colPos	column position of the upper left corner of 'block'
	 */
	public Block (int length, int width, int rowPos, int colPos) {
		this.myRowInterval = new Interval (rowPos, (rowPos + length - 1));
		this.myColInterval = new Interval (colPos, (colPos + width - 1));
	}
	
	/**
	 * Constructor to construct a block with the given intervals
	 * @param rowInterval	row Interval
	 * @param colInterval	column Interval
	 */
	public Block (Interval rowInterval, Interval colInterval) {
		this.myRowInterval = rowInterval;
		this.myColInterval = colInterval;
	}
	
	/**
	 * Obtains the upper left hand coordinates of this Block by getting the left end points of 
	 * the two intervals that represent this block
	 * @return integer array containing the upper left hand coordinates of this Block
	 */
	public int [ ] getCoordinates ( ) {
		// temporary variable to store the result
		int [ ] result = new int [2];
		// generate an integer array containing the co-ordinates of this Block object
		result [0] = this.myRowInterval.myLeftEndPoint;
		result [1] = this.myColInterval.myLeftEndPoint;
		return result;		
	}
	
	public int getLength ( ) {
		return (this.myRowInterval.myRightEndPoint - this.myRowInterval.myLeftEndPoint) + 1;
	}
	
	public int getWidth ( ) {
		return (this.myColInterval.myRightEndPoint - this.myColInterval.myLeftEndPoint) + 1;
	}
	
	public Block moveUp (int trayLength, int trayWidth) {
		// returns null if moving will put the block outside the boundaries of the tray
		if (this.myRowInterval.myLeftEndPoint == 0) {
			if (blockMovementFlag)
				Debug.debugPrint (this.toString ( ) + " cannot move up.");
			return null;
		}
		if (blockMovementFlag)
			Debug.debugPrint (this.toString ( ) + " has moved up one unit.");	
		return new Block (this.myRowInterval.decrement ( ), this.myColInterval);
	}
	
	public Block moveDown (int trayLength, int trayWidth) {
		// returns null if moving will put the block outside the boundaries of the tray
		if (this.myRowInterval.myRightEndPoint == trayLength - 1) {
			if (blockMovementFlag)
				Debug.debugPrint (this.toString ( ) + " cannot move down.");
			return null;
		}
		if (blockMovementFlag)
			Debug.debugPrint (this.toString ( ) + " has moved down one unit.");
		return new Block (this.myRowInterval.increment ( ), this.myColInterval);
	}
	
	public Block moveLeft (int trayLength, int trayWidth) {
		// returns null if moving will put the block outside the boundaries of the tray
		if (this.myColInterval.myLeftEndPoint == 0) {
			if (blockMovementFlag)
				Debug.debugPrint (this.toString ( ) + " cannot move left.");
			return null;
		}
		if (blockMovementFlag)
			Debug.debugPrint (this.toString ( ) + " has moved left one unit.");
		return new Block (this.myRowInterval, this.myColInterval.decrement ( ));
	}
	
	public Block moveRight (int trayLength, int trayWidth) {
		// returns null if moving will put the block outside the boundaries of the tray
		if (this.myColInterval.myRightEndPoint == trayWidth - 1) {
			if (blockMovementFlag)
				Debug.debugPrint (this.toString ( ) + " cannot move right.");
			return null;
		}
		if (blockMovementFlag)
			Debug.debugPrint (this.toString ( ) + " has moved right one unit.");
		return new Block (this.myRowInterval, this.myColInterval.increment ( ));
	}
	
	/**
	 * Checks if this block intersects with argument block
	 * @param 	otherBlock 	block object to check for intersection with this Block
	 * @return 				true, if this Block intersects with argument Block
	 */
	public boolean intersectsWith (Block otherBlock) {
		return (this.myColInterval.intersectsWith (otherBlock.myColInterval)) &&
			   (this.myRowInterval.intersectsWith (otherBlock.myRowInterval));
	}

	/**
	 * Overrides the 'equals' method of the Object class;
	 * two Block objects are considered equal if and only if
	 * the two Interval objects they each contain are respectively equal
	 * @param obj	object being tested for equality against this object
	 * @return		true, if the two Interval objects are equal
	 */
	public boolean equals (Object obj) {
		return (this.myRowInterval.equals (((Block) obj).myRowInterval)) &&
			   (this.myColInterval.equals (((Block) obj).myColInterval));
	}
	
	public String toString ( ) {
		StringBuilder result = new StringBuilder ( );
		result.append ("Block of ").append ("length ").append (this.getLength ( ));
		result.append (" width ").append (this.getWidth ( )).append (" at (");
		result.append (this.myRowInterval.myLeftEndPoint).append (", ").append (this.myColInterval.myLeftEndPoint).append (")");
		return result.toString ( );
	}
	
	public int hashCode ( ) {
		return (this.myRowInterval.myLeftEndPoint * 257) + this.myColInterval.myLeftEndPoint;
	}
	
	/**
	 * Set the boolean flag that tracks the movement of a singular block
	 */
	public void trackMovement ( ) {
		blockMovementFlag = true;
	}
	
	/**
	 * Block class 'Interval', which provides an internal representation
	 * for the row-span and column-span of a block
	 */
	protected static class Interval {

		private int myLeftEndPoint, myRightEndPoint;
		
		public Interval ( ) {
			this (0, 0);
		}

		/**
		 * Constructor to construct an interval object given the left and right endpoints it represents
		 * @param leftEndPoint	positive integer representing the leftmost endpoint of the interval
		 * @param rightEndPoint	positive integer representing the rightmost endpoint of the interval
		 */
		public Interval (int leftEndPoint, int rightEndPoint) {
			this.myLeftEndPoint = leftEndPoint;
			this.myRightEndPoint = rightEndPoint;
		}
		
		/**
		 * Creates a new interval with the end points increased by one;
		 * the width of the interval represented remains constant
		 * Precondition: interval can be incremented
		 * @return 	an interval object whose end points are one more than the end points of this interval object
		 */
		public Interval increment ( ) {
			return new Interval ((this.myLeftEndPoint + 1), (this.myRightEndPoint + 1)); 
		}
		
		/**
		 * Create a new interval with the end points decrease by one;
		 * the width of the interval represented remains constant
		 * Precondition: interval can be decremented
		 * @return 	an interval object whose end points are one less than the end points of this interval object
		 */
		public Interval decrement ( ) {
			return new Interval ((this.myLeftEndPoint - 1), (this.myRightEndPoint - 1));
		}
		
		public int getLeftEnd ( ) {
			return this.myLeftEndPoint;
		}
		
		public int getRightEnd ( ) {
			return this.myRightEndPoint;
		}
		
		/**
		 * Determines if this interval object intersects with another interval object
		 * @param other		the interval object against which this interval object is checked for intersection
		 * @return			true, if this Interval object and the argument Interval object 'other' intersect
		 */
		public boolean intersectsWith (Interval other) {
			// Cases:
			// (a) If other.myLeftEndPoint <= this.myLeftEndPoint, then other.myRightEndPoint >= this.myLeftEndPoint
			// (b) If other.myLeftEndPoint = this.myLeftEndPoint, then all match
			// (c) If other.myLeftEndPoint >= this.myLeftEndPoint, then other.myLeftEndPoint <= this.myRightEndPoint
			// (d) If this.myLeftEndPoint <= other.myLeftEndPoint, then this.myRightEndPoint >= other.myLeftEndPoint
			// (e) If this.myLeftEndPoint = other.myLeftEndPoint, then all match
			// (f) If this.myLeftEndPoint >= other.myLeftEndPoint, then this.myLeftEndPoint <= other.myRightEndPoint
			return ( ((this.myLeftEndPoint <= other.myLeftEndPoint) &&
					  (this.myRightEndPoint >= other.myLeftEndPoint)) 
					  ||
					 ((this.myLeftEndPoint >= other.myLeftEndPoint) &&
					  (this.myLeftEndPoint <= other.myRightEndPoint)) );
		}
		
		/**
		 * Overrides the 'equals' method of the Object class;
		 * Two interval objects are considered equal if and only if
		 * the left endpoints and the right endpoints of the intervals represented are equal
		 * @param obj	object being tested for equality against this object
		 * @return		true, if the intervals are equal
		 */
		public boolean equals (Object obj) {
			return (this.myLeftEndPoint == (((Interval) obj).myLeftEndPoint)) &&
				   (this.myRightEndPoint == (((Interval) obj).myRightEndPoint));
		}
		
		public String toString ( ) {
			return "[ " + this.myLeftEndPoint + ", " + this.myRightEndPoint + " ]";
		}
		
		public int hashCode ( ) {
			return (this.myLeftEndPoint * 257) + this.myRightEndPoint;
		}
	} // End of Interval class
}
